﻿#region Usings
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;

using BK.BlusinessLogic;
using BK.Model;
using BK.Repositories.Abstract;

using Lib.Data.CSV;
using Lib.General;
using Lib.RegularExpressions;
using Lib.Utils.IO;
using Lib.Utils.Validation;
using Lib.Utils.WorkerUtils;
#endregion

namespace BK.Import
{
    public abstract class BaseExpenseFilesWorkerItem<TExpense, TExpenseFile, TExpensesFilesBl, TExpensesFileRepository> :
        BaseFileSystemWorkerItem<string>
        where TExpense : ICsvEntity, new()
        where TExpenseFile : ImportedFile, new()
        where TExpensesFilesBl : IExpensesFilesBl<TExpense, TExpenseFile>
    {
        #region Readonly & Static Fields
        private readonly TExpensesFilesBl _expensesFilesBl;
        private readonly UsersBl _usersBl;
        #endregion

        #region C'tors
        public BaseExpenseFilesWorkerItem(IUsersRepository usersRepository,
            TExpensesFileRepository expensesFilesRepository)
            : this(typeof(TExpenseFile).Name + "_")
        {
            _usersBl = new UsersBl(usersRepository);
            Prepare();
            _expensesFilesBl = DoCreateExpensesFilesBl(expensesFilesRepository);
        }

        private BaseExpenseFilesWorkerItem(string settingsParamsPreffix) : base(settingsParamsPreffix)
        {
        }
        #endregion

        #region Instance Methods
        protected abstract void DoBeforeDoExecute();
        protected abstract TExpensesFilesBl DoCreateExpensesFilesBl(TExpensesFileRepository expensesFileRepository);

        protected abstract void DoTieFileToUser(User user, TExpenseFile expensesFile);

        protected override void DoExecute(string objectToProcess)
        {
            DoBeforeDoExecute();
            string email = GetEmailFromFileName(objectToProcess);
            string content = File.ReadAllText(objectToProcess, Encoding.Default);
            CsvReader csvReader = new CsvReader(Encoding.Default, content, ';');
            CsvReadingResult<TExpense> csvReadingResult = csvReader.ReadAsEntities<TExpense>();
            if (csvReadingResult.IsValid)
            {
                TExpenseFile expensesFile = new TExpenseFile();
                expensesFile.Content = new UTF8Encoding().GetBytes(content);
                expensesFile.OriginalFileName = objectToProcess;

                csvReadingResult.Items.ForEach(expense => _expensesFilesBl.AddExpense(expensesFile, expense));
                User user = _usersBl.GetByEmail(email);
                if (user == null)
                {
                    throw new ImportException(string.Format("There is no user with email {0}", email));
                }

                expensesFile.ImportingTimeStamp = DateTime.Now;
                DoTieFileToUser(user, expensesFile);
                _usersBl.Save(user);

                DeleteAndTryAgainIfFail(objectToProcess);
            }
            else
            {
                MoveAndTryAgainIfFail(objectToProcess, csvReadingResult);
            }
        }

        protected override string GetFirstUnprocessedObject()
        {
            IEnumerable<string> fileNames = Directory.EnumerateFiles(Settings.FolderToMonitor, "*.csv",
                SearchOption.AllDirectories);
            string fileName = (from fn in fileNames
                               where !ExPath.GetDirectoryName(fn).EndsWith("Fail")
                               select fn).FirstOrDefault();

            if (Settings.FolderToMonitor.ToLower() == Path.GetPathRoot(fileName))
            {
                throw new WorkerItemException(
                    string.Format("File should be in subfolder with name equal some user' emale. Wrong file is '{0}'",
                        fileName));
            }

            return fileName;
        }

        private void DeleteAndTryAgainIfFail(string objectToProcess)
        {
            do
            {
                try
                {
                    File.Delete(objectToProcess);
                }
                catch (Exception ex)
                {
                    Logger.Error(GetType(), ex);
                    Thread.Sleep(3000);
                }
            }
            while (File.Exists(objectToProcess));
        }

        private void MoveAndTryAgainIfFail(string objectToProcess, CsvReadingResult<TExpense> csvReadingResult)
        {
            string destinationDirectory = Path.GetDirectoryName(objectToProcess) + "\\Fail";
            Directory.CreateDirectory(destinationDirectory);
            string destFileName = string.Format(@"{0}\{1}", destinationDirectory, Path.GetFileName(objectToProcess));
            do
            {
                try
                {
                    File.Move(objectToProcess, destFileName);
                }
                catch (Exception ex)
                {
                    Logger.Error(GetType(), ex);
                    Thread.Sleep(3000);
                }
            }
            while (File.Exists(objectToProcess) && (!File.Exists(destFileName)));

            using (
                StreamWriter streamWriter = new StreamWriter(Path.ChangeExtension(destFileName, ".dsc"), false,
                    Encoding.Default))
            {
                foreach (CsvRowValidationResult csvRowValidationResult in csvReadingResult.FailedAsReaOnly())
                {
                    ReadOnlyCollection<IValidationResult> failedAsReaOnly = csvRowValidationResult.FailedAsReaOnly();
                    if (failedAsReaOnly.Count > 0)
                    {
                        StringBuilder stringBuilder = new StringBuilder();
                        foreach (IValidationResult validationResult in failedAsReaOnly)
                        {
                            stringBuilder.Append(validationResult).Append("; ");
                        }
                        streamWriter.WriteLine(stringBuilder);
                    }
                }
            }
        }

        private void Prepare()
        {
            try
            {
                ExDirectory.CreateIfNotExists(Settings.FolderToMonitor);

                User[] users = _usersBl.GetAll();

                foreach (User user in users)
                {
                    string userDataDirectoryPath = string.Format("{0}\\{1}", Settings.FolderToMonitor, user.Email);
                    ExDirectory.CreateIfNotExists(userDataDirectoryPath);
                }
            }
            catch (Exception ex)
            {
                Logger.Error(this, ex);
                throw;
            }
        }
        #endregion

        #region Instance Properties
        protected UsersBl UsersBl
        {
            get { return _usersBl; }
        }
        #endregion

        #region Class Methods
        private static string GetEmailFromFileName(string fileName)
        {
            string name = Path.GetDirectoryName(fileName);

            if (string.IsNullOrEmpty(name))
            {
                throw new WorkerItemException(string.Format("File name '{0}' shold not be in a root directory",
                    fileName));
            }

            string[] strings = name.Split('\\');

            string emailFromFileName = strings[strings.Length - 1];
            Match match = Regex.Match(emailFromFileName, RegExpPatterns.EMAIL);
            if (!match.Success)
            {
                throw new WorkerItemException(
                    string.Format(
                        "The container folder's name shold correspond to email address. Current name is '{0}'",
                        emailFromFileName));
            }

            return emailFromFileName;
        }
        #endregion
    }
}